﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web.UI;
using EPiServer;
using EPiServer.Core;
using EPiServer.Core.Html.StringParsing;
using EPiServer.DataAccess;
using EPiServer.SpecializedProperties;
using Moq;
using NUnit.Framework;
using OpenWaves.Collections;
using OpenWaves.EPiServer.CustomProperties;
using OpenWaves.Testing;

namespace OpenWaves.UnitTests.EPiServer.CustomProperties
{
    [TestFixture]
    public class XhtmlStringPropertyTests
    {
        private class StringCollectionPropertyStub : XhtmlStringPropertyMock<IEnumerable<string>>
        {
            public StringCollectionPropertyStub()
                : base(Enumerable.Empty<string>())
            {
                this.OnDeserializeValue = v => string.IsNullOrEmpty(v) ? new string[0] : v.Split(',');
                this.OnIsDefaultValue = v => v == null || v.IsEmpty();
                this.OnSerializeValue = v => v.JoinStrings(",");
            }
        }

        private static void WhenLoadingDataSerializeMethodCannotBeCalled<T>(object value,
                                                                            Func<string, T> onDeserializeValue)
        {
            var property = new XhtmlStringPropertyMock<T>
                               {
                                   OnDeserializeValue = onDeserializeValue,
                                   OnSerializeValue = v =>
                                                          {
                                                              Assert.Fail(
                                                                  "Serialization cannot be performed during laoding property value");
                                                              throw new NotSupportedException();
                                                          }
                               };

            property.LoadData(value);
        }

        private class XhtmlStringPropertyMock<T> : XhtmlStringProperty<T>
        {
            private readonly T defaultValue;
            public Func<string, T> OnDeserializeValue;
            public Func<T, bool> OnIsDefaultValue;
            public Func<T, string> OnSerializeValue;

            public XhtmlStringPropertyMock(T defaultValue)
            {
                this.defaultValue = defaultValue;

                this.OnIsDefaultValue = x => Equals(x, defaultValue);
                this.SetDefaultValue();
            }

            public XhtmlStringPropertyMock()
                : this(default(T))
            {
            }

            protected override T DefaultValue
            {
                get { return this.defaultValue; }
            }

            protected override bool IsDefaultValue(T value)
            {
                return this.OnIsDefaultValue(value);
            }

            protected override XhtmlStringProperty<T> CreateInstance(T value)
            {
                return new XhtmlStringPropertyMock<T>
                           {OnSerializeValue = this.OnSerializeValue, OnDeserializeValue = this.OnDeserializeValue};
            }

            protected override string SerializeValue(T value)
            {
                if (this.OnSerializeValue != null)
                {
                    return this.OnSerializeValue(value);
                }

                throw new NotImplementedException();
            }

            protected override T DeserializeValue(string serializedValue)
            {
                if (this.OnDeserializeValue != null)
                {
                    return this.OnDeserializeValue(serializedValue);
                }

                throw new NotImplementedException();
            }
        }

        private class MyFragmentParser : IFragmentParser
        {
            public StringFragmentCollection Parse(string content, FragmentParserMode parserMode, bool evaluateHash)
            {
                return new StringFragmentCollection(content.ToArray().Select(i => new MyStringFragment(i)));
            }

            private class MyStringFragment : IStringFragment
            {
                private char value;

                public MyStringFragment(char value)
                {
                    this.value = value;
                }

                public void RemapPermanentLinkReferences(IDictionary<Guid, Guid> idMap)
                {
                    this.value++;
                }

                public IList<Guid> ReferencedPermanentLinkIds
                {
                    get { throw new NotImplementedException(); }
                }

                public string GetEditFormat()
                {
                    return this.value.ToString();
                }

                public string GetOnPageEditFormat()
                {
                    throw new NotImplementedException();
                }

                public string GetViewFormat()
                {
                    throw new NotImplementedException();
                }

                public Control GetControl(PageBase hostPage)
                {
                    throw new NotImplementedException();
                }

                public string InternalFormat
                {
                    get { throw new NotImplementedException(); }
                }
            }
        }

        [Test]
        public void AfterRemappingPermanentLinkReferencesPropertyReturnsUpdatedValue()
        {
            using (new EPiServerInfrastructureStub.FragmentParserScope(() => new MyFragmentParser()))
            {
                var property = new XhtmlStringPropertyMock<IEnumerable<char>>(new char[0]);
                property.OnSerializeValue = s => new string(s.ToArray());
                property.OnDeserializeValue = s => s.ToArray();

                property.Value = new[] {'A', 'B'};

                property.RemapPermanentLinkReferences(new Dictionary<Guid, Guid>());

                Assert.That(property.Value, Is.EquivalentTo(new[] {'B', 'C'}));
            }
        }

        [Test]
        public void WhenSavingPropertySerializeValueIsNotCalled()
        {
            using (new EPiServerInfrastructureStub.FragmentParserScope(() => new EPiServerInfrastructureStub.NullFragmentParser()))
            {
                var property = new XhtmlStringPropertyMock<IEnumerable<char>>(new char[0]);
                var propertyDataCollection = new PropertyDataCollection();
                propertyDataCollection.Add(property);

                property.OnSerializeValue = s => new string(s.ToArray());
                property.OnDeserializeValue = s => s.ToArray();

                property.Value = new[] {'A', 'B'};

                property.OnSerializeValue = c =>
                                                {
                                                    Assert.Fail("Serialize value should not be called after RemapPermanentLinkReferences has been called.");
                                                    return String.Empty;
                                                };


                property.SaveData(propertyDataCollection);
            }
        }

        [Test]
        public void AfterPropertyValueIsChangedSaveDataReturnsUpdatedStringRepresentation()
        {
            using (new EPiServerInfrastructureStub.FragmentParserScope(() => new EPiServerInfrastructureStub.NullFragmentParser()))
            {
                var property = new XhtmlStringPropertyMock<IEnumerable<char>>(new char[0]);
                var propertyDataCollection = new PropertyDataCollection();
                propertyDataCollection.Add(property);

                property.OnSerializeValue = s => new string(s.ToArray());
                property.OnDeserializeValue = s => s.ToArray();

                property.Value = new[] { 'A', 'B' };
                Assert.That(property.SaveData(propertyDataCollection), Is.EqualTo("AB"));
            
                property.Value = new[] { 'B', 'A' };
                Assert.That(property.SaveData(propertyDataCollection), Is.EqualTo("BA"));
            }
        }

        [Test]
        public void WhenGettingValueForLazyLoadedPropertyWithDefaultValueGetValueReturnsDefaultValue()
        {
            var property = new StringCollectionPropertyStub();

            var dataRecord = new Mock<IDataRecord>();
            dataRecord.Setup(r => r.FieldCount).Returns(2);

            dataRecord.Setup(r => r.GetName(0)).Returns("Guid");
            dataRecord.Setup(r => r.IsDBNull(0)).Returns(false);
            dataRecord.Setup(r => r.GetGuid(0)).Returns(Guid.Empty);

            dataRecord.SetupGet(p => p["key"]).Returns(DBNull.Value);

            ((IPropertyLoadDataHandler)property).Load("key", dataRecord.Object);

            property.MakeReadOnly();

            var value = property.GetValue();

            CollectionAssert.AreEquivalent(Enumerable.Empty<string>(), value);
        }

        [Test]
        public void WhenGettingValueForLazyLoadedPropertyWithNonDefaultValueGetValueReturnsValue()
        {
            using (new EPiServerInfrastructureStub.FragmentParserScope(() => new EPiServerInfrastructureStub.NullFragmentParser()))
            {
                var databaseFactory = new Mock<DatabaseFactory>();
                databaseFactory.Setup(f => f.Execute(It.IsAny<DatabaseFactory.Action<string>>())).Returns("long,string,value");

                using (PropertyValueScope.Create(() => DataAccessBase.DatabaseFactory, v => DataAccessBase.DatabaseFactory = v, databaseFactory.Object))
                {
                    var property = new StringCollectionPropertyStub();

                    var dataRecord = new Mock<IDataRecord>();
                    dataRecord.Setup(r => r.FieldCount).Returns(2);

                    dataRecord.Setup(r => r.GetName(0)).Returns("Guid");
                    dataRecord.Setup(r => r.IsDBNull(0)).Returns(false);
                    dataRecord.Setup(r => r.GetGuid(0)).Returns(Guid.NewGuid());

                    dataRecord.SetupGet(p => p["key"]).Returns(DBNull.Value);

                    ((IPropertyLoadDataHandler)property).Load("key", dataRecord.Object);

                    property.MakeReadOnly();

                    var value = property.GetValue();

                    CollectionAssert.AreEquivalent(new[] { "long", "string", "value" }, value);
                }
            }
        }

        [Test]
        public void WhenGettingValueForPropertyWithDefaultValueReturnsDefaultValue()
        {
            var property = new StringCollectionPropertyStub();

            property.MakeReadOnly();

            var value = property.GetValue();

            CollectionAssert.AreEquivalent(Enumerable.Empty<string>(), value);
        }

        [Test]
        public void WhenGettingValueForPropertyWithNonDefaultValueReturnsValue()
        {
            var property = new StringCollectionPropertyStub();
            property.LoadData("a,b,c");

            property.MakeReadOnly();
            

            var value = property.GetValue();

            CollectionAssert.AreEquivalent(new [] {"a", "b", "c"}, value);
        }

        [Test]
        [Ignore("Not supported scenario")]
        public void WhenLoadingDataFromNotseriazliedValueThenSerializeMethodCannotBeCalled()
        {
            WhenLoadingDataSerializeMethodCannotBeCalled<IEnumerable<string>>(new[] { "deserialized data" }, v => new[] { v });
        }

        [Test]
        public void WhenLoadingDataFromSerializedValueThenSerializeMethodCannotBeCalled()
        {
            WhenLoadingDataSerializeMethodCannotBeCalled<IEnumerable<string>>("serialized value", v => new[] { v });
        }

        [Test]
        public void WhenLoadingNullPropertyIsNullShouldBeTrueAndValueShouldBeDefaultValue()
        {
            using (new EPiServerInfrastructureStub.FragmentParserScope(() => new EPiServerInfrastructureStub.NullFragmentParser()))
            {
                var property = new StringCollectionPropertyStub();

                CollectionAssert.AreEquivalent(Enumerable.Empty<string>(), property.GetValue());
                Assert.IsTrue(property.IsNull);
            }
        }

        [Test]
        public void WhenSettingValueToDefaultValueIsNullIsTrue()
        {
            using (new EPiServerInfrastructureStub.FragmentParserScope(() => new EPiServerInfrastructureStub.NullFragmentParser()))
            {
                var property = new StringCollectionPropertyStub();

                property.SetValue(new[] { "ad" });
                property.SetValue(new string[0]);

                CollectionAssert.AreEquivalent(Enumerable.Empty<string>(), property.GetValue());
                Assert.IsTrue(property.IsNull);
            }
        }
    }
}